use std::{collections::HashMap, ops::Deref, sync::Arc};

use serde::{Deserialize, Serialize};
use QcRender::resources::{Shader, Texture, TextureKind, UniformInfo};

use super::ResourceManager;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Material {
    pub name: String,
    pub shader: Shader,
    uniformInfoList: HashMap<String, UniformInfo>,
    pub gpu_instances: u32,
}

impl Deref for Material {
    type Target = Shader;
    fn deref(&self) -> &Self::Target {
        &self.shader
    }
}

impl Default for Material {
    fn default() -> Self {
        Self {
            name: "default.mtl".to_string(),
            shader: Default::default(),
            uniformInfoList: Default::default(),
            gpu_instances: 1,
        }
    }
}

impl Material {
    pub fn load(material: &Material, resource_manager: Arc<ResourceManager>) -> Self {
        let name = material.shader.name.clone();

        let mut hashmap = HashMap::new();
        let mut slot = 0;
        for (name, uniformInfo) in material.getUniformList().iter() {
            match uniformInfo {
                UniformInfo::Texture(texture) => {
                    let name = texture.getName();
                    if name.contains(".dds") {
                        if let Some(texture) = resource_manager.get(name) {
                            hashmap.insert(
                                format!("Texture{}", slot),
                                UniformInfo::Texture(Texture::from_u8_array(
                                    &texture.file.data,
                                    &name,
                                    TextureKind::rect(),
                                )),
                            );
                            slot += 1;
                        }
                    }
                }
                _ => {}
            }
        }

        let material = Material::default()
            .with_shader(Shader::new(&name))
            .with_name(&material.name)
            .with_uniform_list(hashmap);

        material
    }
    pub fn with_shader(mut self, shader: Shader) -> Self {
        self.shader = shader;
        self
    }

    pub fn with_instances(mut self, instances: u32) -> Self {
        self.gpu_instances = instances;
        self
    }

    pub fn with_name(mut self, name: &str) -> Self {
        self.name = name.to_string();
        self
    }

    pub fn with_uniform_list(mut self, uniform_list: HashMap<String, UniformInfo>) -> Self {
        self.uniformInfoList = uniform_list;
        self
    }

    pub fn bind(&self, empty_texture: &Texture) {
        self.shader.bind();

        let mut slot = 0;
        for (name, uniformInfo) in self.uniformInfoList.iter() {
            match uniformInfo {
                UniformInfo::Texture(texture) => {
                    texture.bind(slot);
                    slot += 1;
                }
                UniformInfo::Vec4(vec4) => {
                    self.shader.set_uniform_vec4(name, vec4);
                }
                UniformInfo::Vec3(vec3) => {
                    self.shader.set_uniform_vec3(name, vec3);
                }
                UniformInfo::I32(i32) => {
                    self.shader.set_uniform_i32(name, *i32);
                }
            }
        }

        if slot == 0 {
            empty_texture.bind(0);
        }
    }

    pub fn addTexture(&mut self, texture: Texture) {
        self.uniformInfoList
            .insert("Texture".to_string(), UniformInfo::Texture(texture));
    }

    pub fn set_uniform_info(&mut self, name: &str, info: UniformInfo) {
        self.uniformInfoList
            .entry(name.to_string())
            .and_modify(|key| *key = info.clone())
            .or_insert(info);
    }

    pub fn getUniformList(&self) -> &HashMap<String, UniformInfo> {
        &self.uniformInfoList
    }
    pub fn setUniformList(&mut self, uniformList: HashMap<String, UniformInfo>) {
        self.uniformInfoList = uniformList;
    }

    pub fn clear_uniform_list(&mut self) {
        self.uniformInfoList.clear();
    }
}
